package commons;


import java.io.UnsupportedEncodingException;
import java.util.UUID;

/**
 * Modified base-64 encoder. Does not comply with standard. It can be safely
 * used in a URL without any special encoding.
 * <p>
 * It also supports the padding sign not to be present when decoding...
 */
public class URLSafeBase64Encoder {
    public static class NotBase64StringException extends RuntimeException {

    }

    /** The equals sign (=) as a byte. */
    public final static byte PADDING_SIGN = '=';

    /** The 64 valid Base64 values. */
    private final static byte[] ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".getBytes();

    /** The ASCII codes of WHITESPACES */
    private final static byte[] WHITESPACE = new byte[] {9, 10, 13, 32};
    private static final int WHITE_SPACE_ENC = -5;

    private static final int PADDING_SIGN_ENC = -1;

    private static final int ILLEGAL_CHAR_ENC = -9;
    
    /**
     * Translates a Base64 value to either its 6-bit reconstruction value or a
     * negative number indicating some other meaning.
     */
    final private static byte[] DECODABET;
    static {
        // fill the decodabet
        DECODABET = new byte[128];
        // by default, all characters are illegal
        for (int i = 0; i < 128; i++) {
            DECODABET[i] = ILLEGAL_CHAR_ENC;
        }
        // ALPHABET
        for (byte i = 0; i < ALPHABET.length; i++) {
            DECODABET[ALPHABET[i]] = i;
        }
        // WHITESPACES
        for (byte i = 0; i < WHITESPACE.length; i++) {
            DECODABET[WHITESPACE[i]] = WHITE_SPACE_ENC;
        }
        // PADDING
        DECODABET[PADDING_SIGN] = PADDING_SIGN_ENC;
    }

    /**
     * Encodes up to three bytes of the array <var>source</var> and writes the
     * resulting four Base64 bytes to <var>destination</var>. The source and
     * destination arrays can be manipulated anywhere along their length by
     * specifying <var>srcOffset</var> and <var>destOffset</var>. This method
     * does not check to make sure your arrays are large enough to accomodate
     * <var>srcOffset</var> + 3 for the <var>source</var> array or
     * <var>destOffset</var> + 4 for the <var>destination</var> array. The
     * actual number of significant bytes in your array is given by
     * <var>numSigBytes</var>.
     * 
     * @param source the array to convert
     * @param srcOffset the index where conversion begins
     * @param numSigBytes the number of significant bytes in your array
     * @param destination the array to hold the conversion
     * @param destOffset the index where output will be put
     * @return the <var>destination</var> array
     * @since 1.3
     */
    private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset) {
        // 1 2 3
        // 01234567890123456789012345678901 Bit position
        // --------000000001111111122222222 Array position from threeBytes
        // --------| || || || | Six bit groups to index ALPHABET
        // >>18 >>12 >> 6 >> 0 Right shift necessary
        // 0x3f 0x3f 0x3f Additional AND

        // Create buffer with zero-padding if there are only one or two
        // significant bytes passed in the array.
        // We have to shift left 24 in order to flush out the 1's that appear
        // when Java treats a value as negative that is cast from a byte to an
        // int.
        int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
                | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);

        switch (numSigBytes) {
            case 3:
                destination[destOffset] = ALPHABET[(inBuff >>> 18)];
                destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
                destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
                destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f];
                return destination;

            case 2:
                destination[destOffset] = ALPHABET[(inBuff >>> 18)];
                destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
                destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
                destination[destOffset + 3] = PADDING_SIGN;
                return destination;

            case 1:
                destination[destOffset] = ALPHABET[(inBuff >>> 18)];
                destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
                destination[destOffset + 2] = PADDING_SIGN;
                destination[destOffset + 3] = PADDING_SIGN;
                return destination;

            default:
                return destination;
        }
    }

    /**
     * Encodes a byte array into Base64 modified notation.
     * 
     * @param source The data to convert
     * 
     * @since 2.0
     */
    public static String encodeBytes(byte[] source) {
        return encodeBytes(source, 0, source.length);
    } // end encodeBytes

    /**
     * Encodes a byte array into Base64 modified notation.
     * 
     * @param source The data to convert
     * @param off Offset in array where conversion should begin
     * @param len Length of data to convert
     */
    public static String encodeBytes(byte[] source, int off, int len) {
        int len43 = len * 4 / 3;
        byte[] outBuff = new byte[(len43) // Main 4:3
                + ((len % 3) > 0 ? 4 : 0) // Account for padding
        ];

        int d = 0;
        int e = 0;
        int len2 = len - 2;
        int lineLength = 0;
        for (; d < len2; d += 3, e += 4) {
            encode3to4(source, d + off, 3, outBuff, e);
            lineLength += 4;
        }

        if (d < len) {
            encode3to4(source, d + off, len - d, outBuff, e);
            e += 4;
        }
        return new String(outBuff, 0, e);
    }

    /**
     * Decodes four bytes from array <var>source</var> and writes the resulting
     * bytes (up to three of them) to <var>destination</var>. The source and
     * destination arrays can be manipulated anywhere along their length by
     * specifying <var>srcOffset</var> and <var>destOffset</var>. This method
     * does not check to make sure your arrays are large enough to accomodate
     * <var>srcOffset</var> + 4 for the <var>source</var> array or
     * <var>destOffset</var> + 3 for the <var>destination</var> array. This
     * method returns the actual number of bytes that were converted from the
     * Base64 encoding.
     * 
     * 
     * @param source the array to convert
     * @param srcOffset the index where conversion begins
     * @param destination the array to hold the conversion
     * @param destOffset the index where output will be put
     * @return the number of decoded bytes converted
     * @since 1.3
     */
    private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset) {
        // Example: Dk== or Dk
        if (source[srcOffset + 2] == PADDING_SIGN) {
            // Two ways to do the same thing. Don't know which way I like best.
            // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
            // )
            // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
            int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12);

            destination[destOffset] = (byte) (outBuff >>> 16);
            return 1;
        }

        // Example: DkL= or DkL
        else if (source[srcOffset + 3] == PADDING_SIGN) {
            // Two ways to do the same thing. Don't know which way I like best.
            // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
            // )
            // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
            // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
            int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6);

            destination[destOffset] = (byte) (outBuff >>> 16);
            destination[destOffset + 1] = (byte) (outBuff >>> 8);
            return 2;
        }

        // Example: DkLE
        else {
            // Two ways to do the same thing. Don't know which way I like
            // best.
            // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 )
            // >>> 6 )
            // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
            // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
            // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
            int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6)
                    | ((DECODABET[source[srcOffset + 3]] & 0xFF));

            destination[destOffset] = (byte) (outBuff >> 16);
            destination[destOffset + 1] = (byte) (outBuff >> 8);
            destination[destOffset + 2] = (byte) (outBuff);

            return 3;
        }
    } // end decodeToBytes

    /**
     * Very low-level access to decoding ASCII characters in the form of a byte
     * array. Does not support automatically gunzipping or any other "fancy"
     * features.
     * 
     * @param source The Base64 encoded data
     * @param off The offset of where to begin decoding
     * @param len The length of characters to decode
     * @return decoded data
     * @since 1.3
     */
    public static byte[] decode(byte[] source, int off, int len) throws NotBase64StringException {
        int len34 = len * 3 / 4;
        byte[] outBuff = new byte[len34]; // Upper limit on size of output
        int outBuffPosn = 0;

        byte[] b4 = new byte[4];
        int b4Posn = 0;
        int i = 0;
        byte sbiCrop = 0;
        byte sbiDecode = 0;
        for (i = off; i < off + len; i++) {
            sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
            sbiDecode = DECODABET[sbiCrop];

            if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or
            // better
            {
                if (sbiDecode >= PADDING_SIGN_ENC) {
                    b4[b4Posn++] = sbiCrop;
                    if (b4Posn > 3 || i == off + len - 1) {
                        if (i == off + len - 1) {
                            // hack if no padding sign => pray !
                            System.arraycopy(new byte[] {PADDING_SIGN, PADDING_SIGN, PADDING_SIGN, PADDING_SIGN}, 0, b4, b4Posn, 4 - b4Posn);
                        }
                        outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn);
                        b4Posn = 0;

                        // If that was the equals sign, break out of 'for' loop
                        if (sbiCrop == PADDING_SIGN)
                            break;
                    } // end if: quartet built

                } // end if: equals sign or better

            } // end if: white space, equals sign or better
            else {
                throw new NotBase64StringException();
            } // end else:
        } // each input character

        byte[] out = new byte[outBuffPosn];
        System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
        return out;
    } // end decode

    public static byte[] decode(String str) throws NotBase64StringException {
        return decode(str.getBytes(), 0, str.length());
    }

    /**
     * Convert a uuid string to a base 64 encoded string. If the given string is
     * not an uuid, the result of this method is not defined.
     * 
     * @param uuid
     */
    public static String uuidToBase64String(String uuid) {
       UUID u = UUID.fromString(uuid);
       byte[] uc = new byte[16];
       System.arraycopy(longToByteArray(u.getMostSignificantBits()), 0, uc, 0, 8);
       System.arraycopy(longToByteArray(u.getLeastSignificantBits()), 0, uc, 8, 8);
       String ret =  encodeBytes(uc);
       int i = ret.indexOf(PADDING_SIGN);
       return i < 0 ? ret : ret.substring(0, i);
       
    }

    /**
     * Convert a base64 String as an uuid, assuming it was encoded using
     * uuidToBase64String.
     * 
     * @param base64
     * @return
     */
    public static String base64StringToUUID(String base64) {
        byte[] uc = decode(base64);
        if(uc.length!=16){
            throw new IllegalStateException("Are you sure the input is a base 64 encoded uuid ?");
        }
        return new UUID(byteArrayToLong(uc, 0), byteArrayToLong(uc, 8)).toString();
    }

    /**
     * Encode into a base64 string the UTF-8 representation of the given string
     * 
     * @param toEncode
     * @return
     */
    public static String encodeUTF8String(String toEncode) {
        try {
            return encodeBytes(toEncode.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new Error("UTF-8 not supported !", e);
        }
    }
    /**
     * Decode to a String the base64 encoded representation of the UTF-8 string.
     * 
     * @param base64ToDecode
     * @return
     */
    public static String decodeUTF8String(String base64ToDecode) {
         try {
            return new String(decode(base64ToDecode), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new Error("UTF-8 not supported !", e);
        }
    }
    
    
    static long byteArrayToLong(byte[] array, int offset) {
        long ret = 0;
        for (int i = 0; i < 8; i++) {
            ret <<= 8;
            ret |= array[7 - i + offset] & 0xFF; 
        }
        return ret;
    }
    
    static byte[] longToByteArray(long l) {
        byte[] ret = new byte[8];
        for (int i = 0; i < 8; i++) {
            ret[i] = (byte) (l & 0xFFl);
            l >>>= 8;
        }
        return ret;
    }
    
    public static void main(String[] args) {
        long l = 0xFFF0FF123456FFFFl;
        System.out.println(Long.toHexString(l));
        byte[] ba = URLSafeBase64Encoder.longToByteArray(l);

        long l2 = URLSafeBase64Encoder.byteArrayToLong(ba, 0);
        System.out.println(Long.toHexString(l2));

        //byte b = -1;

        long toto = 0;
        System.out.println(Long.toHexString(toto));
    }

}
